#include "gtest/gtest.h"
#include "world/WorldFactory.h"
#include "cell/GenericCellStateChars.h"
#include "world/World.h"
#include "world/GenericWorldSize.h"
#include <iostream>
#include <sstream>

template<int ROW, int COL>
struct FtWorld : testing::Test
{
    FtWorld() : world(WorldFactory::create())
    {}

protected:

    void startWith(const char* init)
    {
        std::cout<<"***world start with :***"<<std::endl;
        world.startWith(init, ft_chars, GenericWorldSize<ROW, COL>());
        output();
    }

    void evolve(int times)
    {
        std::cout<<"***after evolve "<<times<<" times:***"<<std::endl;
        for(int i = 0; i< times; i ++)
        {
            world.nextGeneration();
        }
        std::cout<<"***world would be :***"<<std::endl;
        output();
    }

    void checkWorld(const char* expect)
    {
        ASSERT_TRUE(isEq(world, expect));
    }

private:
    bool isEq(World& ret, const char* right)
    {
        std::ostringstream ss;
        ret.output(ss, ft_chars);

        const char*  left = ss.str().c_str();

        int offset = 0;
        for(int i = 0; i< ROW * COL ; i++)
        {
            if(left[(i+offset)] == '\n') offset++;//filter the \n
            if(left[(i+offset)] != right [i])
                return false;
        }
        return true;
    }

    void output()
    {
        world.output(std::cout, input_chars);
    }

private:
    World& world;
};

struct FtWorld_3_3: FtWorld<3,3>
{
};

TEST_F(FtWorld_3_3, should_dead_board_when_1_neighbor)
{
    const char init[] = {1,0,0,
                         0,0,0,
                         0,0,0
    };
    startWith(init);
    evolve(1);

    const char next[] = {0,0,0,
                         0,0,0,
                         0,0,0
    };
    checkWorld(next);
}

TEST_F(FtWorld_3_3, should_stay_alive_when_2_neighbor)
{
    const char init[] = {1,0,0,
                         1,1,0,
                         0,0,0
    };
    const char next[] = {1,1,0,
                         1,1,0,
                         0,0,0
    };
    startWith(init);
    evolve(1);
    checkWorld(next);
}

TEST_F(FtWorld_3_3, should_stay_dead_when_2_neighbor)
{
    const char init[] = {1,0,0,
                         0,0,0,
                         0,0,1
    };
    const char next[] = {0,0,0,
                         0,0,0,
                         0,0,0
    };
    startWith(init);
    evolve(1);
    checkWorld(next);
}

TEST_F(FtWorld_3_3, should_ALIVE_board_when_3_neighbor)
{
    const char init[] = {1,0,1,
                         1,0,0,
                         0,0,0
    };
    const char next[] = {0,1,0,
                         0,1,0,
                         0,0,0
    };
    startWith(init);
    evolve(1);
    checkWorld(next);
}

TEST_F(FtWorld_3_3, should_DEAD_board_when_4_neighbor)
{
    const char init[] = {1,0,1,
                         1,1,0,
                         0,0,1
    };
    const char next[] = {1,0,0,
                         1,0,1,
                         0,1,0
    };
    startWith(init);
    evolve(1);
    checkWorld(next);
}

TEST_F(FtWorld_3_3, should_DEAD_board_when_5_neighbor)
{
    const char init[] = {1,0,1,
                         1,1,1,
                         0,0,1
    };
    const char next[] = {1,0,1,
                         1,0,1,
                         0,0,1
    };
    startWith(init);
    evolve(1);
    checkWorld(next);
}

TEST_F(FtWorld_3_3, should_DEAD_board_when_6_neighbor)
{
    const char init[] = {1,0,1,
                         1,1,1,
                         0,1,1
    };
    const char next[] = {1,0,1,
                         1,0,0,
                         1,0,1
    };
    startWith(init);
    evolve(1);
    checkWorld(next);
}

TEST_F(FtWorld_3_3, should_DEAD_board_when_7_neighbor)
{
    const char init[] = {1,0,1,
                         1,1,1,
                         1,1,1
    };
    const char next[] = {1,0,1,
                         0,0,0,
                         1,0,1
    };
    startWith(init);
    evolve(1);
    checkWorld(next);
}

TEST_F(FtWorld_3_3, should_DEAD_board_when_8_neighbor)
{
    const char init[] = {1,1,1,
                         1,1,1,
                         1,1,1
    };
    const char next[] = {1,0,1,
                         0,0,0,
                         1,0,1
    };
    startWith(init);
    evolve(1);
    checkWorld(next);
}

TEST_F(FtWorld_3_3, should_be_stble_when_generate_sevral_times)
{
    const char init[] = {1,1,1,
                         0,1,1,
                         1,1,0
    };
    startWith(init);

    evolve(200);

    const char next[] = {0,0,0,
                         0,0,0,
                         0,0,0
    };

    checkWorld(next);
}

struct FtWorld_4_4 : FtWorld<4,4>
{
};


TEST_F(FtWorld_4_4, should_get_sec_board_when_give_init_borad)
{

    const char init_board[] = {0,1,1,1,
            0,1,1,0,
            1,0,0,1,
            1,1,1,0
    };

    const char sec_board[] = {0,1,0,1,
            1,0,0,0,
            1,0,0,1,
            1,1,1,0
    };
    startWith(init_board);
    evolve(1);
    checkWorld(sec_board);
}

TEST_F(FtWorld_4_4, should_get_third_board_when_give_init_borad_evole_twice)
{

    const char init_board[] = {0,1,1,1,
            0,1,1,0,
            1,0,0,1,
            1,1,1,0
    };
    startWith(init_board);
    evolve(10);

    const char final_board[] = {0,1,1,0,
            1,0,0,1,
            0,1,0,1,
            0,0,1,0
    };

    checkWorld(final_board);
}

const char data[] = {
        0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1 ,
        1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0 ,
        1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0 ,
        0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0 ,
        1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1 ,
        1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1 ,
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1 ,
        0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0 ,
        1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0 ,
        1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0 ,
        1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1 ,
        1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1 ,
        0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1 ,
        1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0 ,
        1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1 ,
        0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0 ,
        0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0 ,
        1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0 ,
        1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0 ,
        1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1
};

const char final_data[] = {
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1 ,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1 ,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
        1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
        1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0 ,
        0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0 ,
        0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

struct FtWorld_20_20 : FtWorld<20,20>
{
};

TEST_F(FtWorld_20_20, should_get_final_board_when_give_init_borad_evole)
{
    startWith(data);
    evolve(200);
    checkWorld(final_data);
}
